package app

import (
	"context"
	"fmt"
	"gitee.com/zhucheer/orange/cfg"
	"gitee.com/zhucheer/orange/logger"
	"gitee.com/zhucheer/orange/utils"
	"net"
	"net/http"
	"os"
	"path/filepath"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"
)

type FileServerType int

// FileServer Params
const (
	AutoIndex = FileServerType(iota)
	DirDeny
)

// Error handlers
var (
	NotFoundHandler = func(c *Context) error {
		c.responseStatus = http.StatusNotFound
		c.responseBody.Write([]byte("Not Found"))
		c.response.Header().Set("Content-Type", "text/plain; charset=utf-8")
		c.response.Header().Set("X-Content-Type-Options", "nosniff")
		return nil
	}
	MethodNotAllowedHandler = func(c *Context) error {
		c.responseStatus = http.StatusMethodNotAllowed
		c.responseBody.Write([]byte("Method Not Allowed"))
		c.response.Header().Set("Content-Type", "text/plain; charset=utf-8")
		c.response.Header().Set("X-Content-Type-Options", "nosniff")
		return nil
	}
)

type OrangeServer struct {
	inShutdown  int32
	httpSrv     *http.Server
	tcpListener *net.TCPListener
	mutex       sync.Mutex
}

type appTcpKeepAliveListener struct {
	*net.TCPListener
}

func (ln appTcpKeepAliveListener) Accept() (net.Conn, error) {
	tc, err := ln.AcceptTCP()
	if err != nil {
		return nil, err
	}
	tc.SetKeepAlive(true)
	tc.SetKeepAlivePeriod(3 * time.Minute)
	return tc, nil
}

func NewSrv(httpSrv *http.Server) *OrangeServer {
	return &OrangeServer{
		httpSrv: httpSrv,
	}
}

func (app *OrangeServer) AppListenAndServe() error {
	if app.shuttingDown() {
		return http.ErrServerClosed
	}
	addr := app.httpSrv.Addr
	if addr == "" {
		addr = ":http"
	}
	graceTag := cfg.GetBoolFlag("grace")
	var ln net.Listener
	var err error
	if graceTag {
		f := os.NewFile(3, "")
		ln, err = net.FileListener(f)
	} else {
		ln, err = net.Listen("tcp", addr)
	}
	if err != nil {
		panic(fmt.Sprintf("tcp lister error:%v", err))
	}

	tcpListener, ok := ln.(*net.TCPListener)
	if !ok {
		panic("orange server listener is not tcp listener")
	}
	app.tcpListener = tcpListener
	if err != nil {
		return err
	}
	app.writePidToFile(os.Getpid())
	logger.Info("orange http server start bind:%v, pid:%d", addr, os.Getpid())
	return app.httpSrv.Serve(appTcpKeepAliveListener{tcpListener})
}

func (app *OrangeServer) GetListener() *net.TCPListener {
	return app.tcpListener
}

func (app *OrangeServer) ShutdownDo(ctx context.Context) error {
	atomic.StoreInt32(&app.inShutdown, 1)
	app.clearPidToFile()
	return app.httpSrv.Shutdown(ctx)
}

// writePidToFile 写入pid到文件
func (app *OrangeServer) writePidToFile(pid int) {
	app.mutex.Lock()
	defer app.mutex.Unlock()
	pidFile := cfg.GetString("app.pidfile", cfg.ConfigDef.GetString("app.pidfile"))
	if pidFile == "" {
		return
	}

	pidFile = strings.Replace(pidFile, "/", utils.DirDot(), -1)
	if pidFile == "" {
		pidFile = "." + utils.DirDot() + "storage" + utils.DirDot() + "orange.pid"
	}
	if pidFile[0:2] == "."+utils.DirDot() {
		pidFile = filepath.Dir(os.Args[0]) + utils.DirDot() + pidFile[2:]
	}
	err := utils.WriteFile(pidFile, strconv.Itoa(pid))
	if err != nil {
		logger.Error("pidfile write pid:%d error:%v", pid, err)
	}
}

func (app *OrangeServer) clearPidToFile() {
	app.mutex.Lock()
	defer app.mutex.Unlock()
	pidFile := cfg.Config.GetString("app.pidfile")
	if pidFile == "" {
		return
	}

	pidFile = strings.Replace(pidFile, "/", utils.DirDot(), -1)
	if pidFile == "" {
		pidFile = "." + utils.DirDot() + "storage" + utils.DirDot() + "orange.pid"
	}
	if pidFile[0:2] == "."+utils.DirDot() {
		pidFile = filepath.Dir(os.Args[0]) + utils.DirDot() + pidFile[2:]
	}

	err := utils.RemoveFile(pidFile)
	if err != nil {
		logger.Error("pidfile clear error:%v", err)
	}

}

func (app *OrangeServer) shuttingDown() bool {
	return atomic.LoadInt32(&app.inShutdown) != 0
}

// noDirListingHandler 禁止显示目录列表
func noDirListingHandler(h http.Handler, pattenPrefix, basePath string) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		relationPath := strings.TrimLeft(r.URL.Path, pattenPrefix)
		indexFilePath := strings.TrimRight(basePath, "/") + "/" + relationPath + "index.html"

		exists, _ := utils.FileExists(indexFilePath)
		if strings.HasSuffix(r.URL.Path, "/") && exists == false {
			HttpForbidden(w, r)
			return
		}
		h.ServeHTTP(w, r)
	})
}
